summaryrefslogtreecommitdiffstats
path: root/src/android/app/src/main/java/org/yuzu/yuzu_emu/dialogs/MotionAlertDialog.java
blob: 874c1acbc1293fadbc85f782e0c0b01f855a50d3 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
package org.yuzu.yuzu_emu.dialogs;

import android.content.Context;
import android.view.InputDevice;
import android.view.KeyEvent;
import android.view.MotionEvent;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;

import org.yuzu.yuzu_emu.features.settings.model.view.InputBindingSetting;
import org.yuzu.yuzu_emu.utils.Log;

import java.util.ArrayList;
import java.util.List;

/**
 * {@link AlertDialog} derivative that listens for
 * motion events from controllers and joysticks.
 */
public final class MotionAlertDialog extends AlertDialog {
    // The selected input preference
    private final InputBindingSetting setting;
    private final ArrayList<Float> mPreviousValues = new ArrayList<>();
    private int mPrevDeviceId = 0;
    private boolean mWaitingForEvent = true;

    /**
     * Constructor
     *
     * @param context The current {@link Context}.
     * @param setting The Preference to show this dialog for.
     */
    public MotionAlertDialog(Context context, InputBindingSetting setting) {
        super(context);

        this.setting = setting;
    }

    public boolean onKeyEvent(int keyCode, KeyEvent event) {
        Log.debug("[MotionAlertDialog] Received key event: " + event.getAction());
        switch (event.getAction()) {
            case KeyEvent.ACTION_UP:
                setting.onKeyInput(event);
                dismiss();
                // Even if we ignore the key, we still consume it. Thus return true regardless.
                return true;

            default:
                return false;
        }
    }

    @Override
    public boolean onKeyLongPress(int keyCode, @NonNull KeyEvent event) {
        return super.onKeyLongPress(keyCode, event);
    }

    @Override
    public boolean dispatchKeyEvent(KeyEvent event) {
        // Handle this key if we care about it, otherwise pass it down the framework
        return onKeyEvent(event.getKeyCode(), event) || super.dispatchKeyEvent(event);
    }

    @Override
    public boolean dispatchGenericMotionEvent(@NonNull MotionEvent event) {
        // Handle this event if we care about it, otherwise pass it down the framework
        return onMotionEvent(event) || super.dispatchGenericMotionEvent(event);
    }

    private boolean onMotionEvent(MotionEvent event) {
        if ((event.getSource() & InputDevice.SOURCE_CLASS_JOYSTICK) == 0)
            return false;
        if (event.getAction() != MotionEvent.ACTION_MOVE)
            return false;

        InputDevice input = event.getDevice();

        List<InputDevice.MotionRange> motionRanges = input.getMotionRanges();

        if (input.getId() != mPrevDeviceId) {
            mPreviousValues.clear();
        }
        mPrevDeviceId = input.getId();
        boolean firstEvent = mPreviousValues.isEmpty();

        int numMovedAxis = 0;
        float axisMoveValue = 0.0f;
        InputDevice.MotionRange lastMovedRange = null;
        char lastMovedDir = '?';
        if (mWaitingForEvent) {
            for (int i = 0; i < motionRanges.size(); i++) {
                InputDevice.MotionRange range = motionRanges.get(i);
                int axis = range.getAxis();
                float origValue = event.getAxisValue(axis);
                float value = origValue;//ControllerMappingHelper.scaleAxis(input, axis, origValue);
                if (firstEvent) {
                    mPreviousValues.add(value);
                } else {
                    float previousValue = mPreviousValues.get(i);

                    // Only handle the axes that are not neutral (more than 0.5)
                    // but ignore any axis that has a constant value (e.g. always 1)
                    if (Math.abs(value) > 0.5f && value != previousValue) {
                        // It is common to have multiple axes with the same physical input. For example,
                        // shoulder butters are provided as both AXIS_LTRIGGER and AXIS_BRAKE.
                        // To handle this, we ignore an axis motion that's the exact same as a motion
                        // we already saw. This way, we ignore axes with two names, but catch the case
                        // where a joystick is moved in two directions.
                        // ref: bottom of https://developer.android.com/training/game-controllers/controller-input.html
                        if (value != axisMoveValue) {
                            axisMoveValue = value;
                            numMovedAxis++;
                            lastMovedRange = range;
                            lastMovedDir = value < 0.0f ? '-' : '+';
                        }
                    }
                    // Special case for d-pads (axis value jumps between 0 and 1 without any values
                    // in between). Without this, the user would need to press the d-pad twice
                    // due to the first press being caught by the "if (firstEvent)" case further up.
                    else if (Math.abs(value) < 0.25f && Math.abs(previousValue) > 0.75f) {
                        numMovedAxis++;
                        lastMovedRange = range;
                        lastMovedDir = previousValue < 0.0f ? '-' : '+';
                    }
                }

                mPreviousValues.set(i, value);
            }

            // If only one axis moved, that's the winner.
            if (numMovedAxis == 1) {
                mWaitingForEvent = false;
                setting.onMotionInput(input, lastMovedRange, lastMovedDir);
                dismiss();
            }
        }
        return true;
    }
}